package com.tmanager.persistence.tx;

import com.google.inject.Inject;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import javax.persistence.EntityTransaction;
import javax.persistence.FlushModeType;

/**
 * @author Paul A. Shmarev
 */
public class TransactionalInterceptor implements MethodInterceptor {

    @Inject
    private EntityManagerProvider provider;

    public TransactionalInterceptor() {
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Transactional annotation = invocation.getMethod().getAnnotation(Transactional.class);
        final boolean creator;
        EntityManagerWrapper emw = provider.getCurrent0();
        if (emw == null || (annotation != null && annotation.requiresNew())) {
            emw = provider.createNew();
            emw.getOriginal().setFlushMode(FlushModeType.COMMIT);
            emw.getOriginal().getTransaction().begin();
            creator = true;
        } else {
            creator = false;
        }
        try {
            Object result = invocation.proceed();
            if (creator) {
                emw.getOriginal().getTransaction().commit();
            }
            return result;
        } catch (Throwable e) {
            if (isRollbackException(annotation, e.getClass())) {
                EntityTransaction tx = provider.getCurrent0().getOriginal().getTransaction();
                if (tx.isActive()) {
                    tx.setRollbackOnly();
                }
            }
            throw e;
        } finally {
            if (creator) {
                provider.release(emw);
                EntityTransaction tx = emw.getOriginal().getTransaction();
                if (tx.isActive() && tx.getRollbackOnly()) {
                    tx.rollback();
                }
                emw.getOriginal().close();
            }
        }
    }

    private static boolean isRollbackException(Transactional annotation, Class<? extends Throwable> exceptionClass) {
        if (annotation == null) {
            return true;
        }
        for (Class<?> c : annotation.rollbackOn()) {
            if (c.isAssignableFrom(exceptionClass)) {
                return true;
            }
        }
        return false;
    }

}
